<?php
/**
 * 文件对象
 * 
 * 封装了文件系统的删除、新建、写入、权限设置等操作
 * 
 * 本类中的权限操作仅限于linux/unix
 *
 * @version $Id: IFile.php 317 2008-05-04 15:33:31Z iwind $
 * @package util.io
 */
class IFile extends IObject {
	private $filename;
	/**
	 * 用户权限
	 *
	 */
	const USER_PERMISSION = 1;
	
	/**
	 * 用户所在组权限
	 *
	 */
	const GROUP_PERMISSION = 2;
	
	/**
	 * 其他组权限
	 *
	 */
	const OTHER_PERMISSION = 4;
	
	/**
	 * 所有组权限
	 *
	 */
	const ALL_PERMISSION = 8;
	
	/**
	 * 构造器
	 *
	 * @param string $filename 文件名
	 */
	public function __construct($filename) {
		$this->filename = $filename;
	}
	
	/**
	 * 判断是否可以执行
	 *
	 * @return boolean
	 */
	public function canExecute() {
		return is_executable($this->filename);
	}
	
	/**
	 * 判断该对象是否可读
	 *
	 * @return boolean
	 */	
	public function canRead() {
		return is_readable($this->filename);
	}
	
	/**
	 * 判断该对象是否可写
	 *
	 * @return boolean
	 */
	public function canWrite() {
		return is_writeable($this->filename);
	}
	
	/**
	 * 创建文件
	 *
	 * @return boolean
	 */
	public function createFile() {
		if (!$this->isFile()) {
			$fp = fopen($this->filename, "w+");
			fclose($fp);
			return ($fp != null)?true:false;
		}
		return true;
	}
	
	/**
	 * 清空当前文件
	 *
	 * @return boolean
	 */
	public function clearFile() {
		if ($this->isFile()) {
			$fp = fopen($this->filename, "w+");
			fclose($fp);
			return ($fp != null)?true:false;
		}
		return true;
	}
	
	/**
	 * 删除当前文件
	 *
	 * 如果是目录，需要保证目录为空
	 * 
	 */
	public function delete() {
		if ($this->isFile()) {
			unlink($this->filename);
		}
		else if ($this->isDirectory()) {
			rmdir($this->filename);
		}
	}
	/**
	 * 检查文件/目录是否存在
	 *
	 * @return boolean
	 */
	public function exists() {
		return file_exists($this->filename);
	}
	
	/**
	 * 取得绝对路径的文件对象
	 *
	 * @return IFile
	 */
	public function getAbsoluteFile() {
		return new IFile($this->getAbsolutePath());
	}
	
	/**
	 * 得到绝对路径
	 *
	 * @return string/null
	 */
	public function getAbsolutePath() {
		if ($this->exists()) {
			return realpath($this->getPath());
		}
		return null;
	}
	
	/**
	 * 取得权限组
	 * 
	 * 结果为：
	 * 
	 * array(IFile::USER_PERMISSION, IFile::GROUP_PERMISSION, IFile::OTHER_PERMISSION, IFile::ALL_PERMISSION)
	 *
	 * @return array
	 * @since 0.0.2
	 */
	public function getPermissionGroup() {
		return array(self::USER_PERMISSION, self::GROUP_PERMISSION, self::OTHER_PERMISSION, self::ALL_PERMISSION);
	}
	
	/**
	 * 得到文件名
	 *
	 * @return string
	 */
	public function getName() {
		return basename($this->filename);
	}
	
	/**
	 * 得到上级目录路径
	 *
	 * @return string/null
	 */
	public function getParent() {
		if ($this->exists()) {
			return dirname($this->filename);
		}
		return null;
	}
	
	/**
	 * 得到上级文件对象
	 *
	 * @return IFile
	 */
	public function getParentFile() {
		return new IFile($this->getParent());
	}
	
	/**
	 * 取得当前对象的路径
	 *
	 * @return string
	 */
	public function getPath() {
		return preg_replace("/[\\/\\\\]+/", self::getDirectorySeparator(), $this->filename);
	}
	
	/**
	 * 判断当前对象的路径是否为绝对路径
	 *
	 * @return boolean
	 */
	public function isAbsolute() {
		$path = $this->getPath();
		if (self::isAbsolutePath($path)) {
			return true;
		}
		return false;
	}
	
	/**
	 * 判断是否是绝对路径
	 *
	 * @param string $path 路径
	 * @return boolean
	 */
	public static function isAbsolutePath($path) {
		return native::isAbsolutePath($path);
	}
	
	/**
	 * 检查当前路径是否为目录
	 *
	 * @return boolean
	 */
	public function isDirectory() {
		return is_dir($this->filename);
	}
	
	/**
	 * 检查当前路径是否为文件
	 *
	 * @return boolean
	 */
	public function isFile() {
		return is_file($this->filename);
	}
	
	/**
	 * 判断当前文件/目录是否为隐藏的
	 * 
	 * 在当前版本中尚未实现
	 *
	 */
	public function isHidden() {
		ILogger::fatal("IFile::isHidden() is not realized in current version yet");
	}
	
	/**
	 * 取得最后修改时间
	 *
	 * @return integer
	 */
	public function getLastModified() {
		if ($this->exists()) {
			return filemtime($this->filename);
		}
		return null;
	}
	
	/**
	 * 取得文件尺寸
	 *
	 * @return integer
	 */
	public function getLength() {
		return filesize($this->filename);
	}
	
	/**
	 * 取得当前目录下的文件列表
	 *
	 * 返回一个包含每一个子文件对象的数组，并排除了.和..两个路径。
	 * 
	 * 从0.0.3开始加入$recursively参数，可以递归地查找一个目录下所有的子文件对象，包括目录。
	 *
	 * @param string $filter 过滤规则，比如*.php
	 * @param boolean $recursively 是否递归查找
	 * @return array
	 */
	public function getList($filter = null, $recursively = false) {
		if ($this->isDirectory()) {
			if ($filter == null) {
				$files = scandir($this->filename);
			} else {
				$files = glob($this->getPath() . self::getDirectorySeparator() . $filter, GLOB_BRACE);
			}
			$list = array();
			$found = array();
			foreach ($files as $file) {
				$file = basename($file);
				$found[] = $file;
				if ($file != "." && $file != "..") {
					$childFile = new IFile($this->getPath() . "/" . $file);
					array_push($list, $childFile);
					
					//递归地加入子文件对象
					if ($recursively && $childFile->isDirectory()) {
						foreach ($childFile->getList($filter, true) as $newChildFile) {
							array_push($list, $newChildFile);
						}
					}
				}
			}
			
			//尝试查找目录不匹配但是文件名匹配的项
			if ($filter != null && $recursively) {
				$files = scandir($this->filename);
				foreach ($files as $file) {
					$file = basename($file);
					if (!in_array($file, $found) && $file != "." && $file != "..") {
						//递归地加入子文件对象
						$childFile = new IFile($this->getPath() . "/" . $file);
						if ($recursively && $childFile->isDirectory()) {
							foreach ($childFile->getList($filter, true) as $newChildFile) {
								array_push($list, $newChildFile);
							}
						}
					}
				}
			}
			
			return $list;
		}
		return array();
	}
	
	/**
	 * 创建目录
	 *
	 * @param integer $mode 模式，使用八进制表示
	 * @return boolean
	 */
	public function mkdir($mode = 0700) {
		if (!$this->exists()) {
			return mkdir($this->filename, $mode);
		}
		return true;
	}
	
	/**
	 * 创建目录
	 *
	 * 与mkdir不同，如果要创建的目录上级不存在，将自动创建
	 * 
	 * @param integer $mode 模式，使用八进制表示
	 * @return string
	 * @see mkdir
	 */
	public function mkdirs($mode = 0700) {
		if (!$this->exists()) {
			return mkdir($this->filename, $mode, true);
		}
		return true;
	}
	
	/**
	 * 改名为新的文件对象
	 *
	 * @param IFile $dest 目标文件对象
	 */
	public function renameTo(IFile $dest) {
		rename($this->filename, $dest->getPath());
	}
	
	/**
	 * 取得文件模式（八进制）
	 *
	 * @return string
	 */
	public function getMode() {
		if ($this->exists()) {
			$perms = fileperms($this->filename);
			$oct = sprintf("%o", $perms);
			return substr($oct, -4);
		}
		return "0000";
	}
	
	######################### 以下方法用于支持模式的计算 ####################
	private function octToBinary($mode) {
		return sprintf("%016s", base_convert($mode, 8, 2));
	}
	
	private function plusMode($newMode) {
		return bindec($this->octToBinary($this->getMode()) | $this->octToBinary($newMode));
	}
	
	private function minusMode($newMode) {
		$a = $this->octToBinary($this->getMode());
		$b = $this->octToBinary($newMode);
		return bindec($a) ^ bindec($a & $b);
		//return bindec($a) & (~bindec($b));
	}
	
	private function getModeByGroup($mode, $group, $number) {
		foreach ($this->getPermissionGroup() as $permissionGroup) {
			if ($permissionGroup & $group) {
				switch($permissionGroup) {
					case self::USER_PERMISSION:
						$mode = substr($mode, 0, 1) . $number . substr($mode, 2);
						break;
					case self::GROUP_PERMISSION:
						$mode = substr($mode, 0, 2) . $number . substr($mode, 3);
						break;
					case self::OTHER_PERMISSION:
						$mode = substr($mode, 0, 3) . $number;
						break;
					case self::ALL_PERMISSION:
						$mode = "0" . str_repeat($number, 3);
						break;
				}
			}
		}
		return $mode;
	}
	######################### 模式计算辅助方法结束 ####################
	
	/**
	 * 设置是否可执行
	 *
	 * @param boolean $executable 是否可执行
	 * @param integer $group 应用到的权限组
	 * @return boolean
	 */
	public function setExecutable($executable, $group = IFile::ALL_PERMISSION) {
		if ($this->exists()) {
			$newMode = "0000";
			$diffMode = $this->getModeByGroup("0000", $group, "1");
			if ($executable) {
				$newMode = $this->plusMode($diffMode);
			}
			else {
				$newMode = $this->minusMode($diffMode);
			}
			return chmod($this->filename, $newMode);
		}
		return false;
	}
	
	/**
	 * 设置文件最后修改时间
	 *
	 * @param integer $time 时间戳
	 * @return boolean
	 */
	public function setLastModified($time) {
		if ($this->exists()) {
			return touch($this->filename, $time);
		}
		return false;
	}
	
	/**
	 * 设置文件是否可读
	 *
	 * @param boolean $readable 是否可读
	 * @param integer $group 应用到的权限组
	 * @return boolean
	 */
	public function setReadable($readable, $group = IFile::ALL_PERMISSION) {
		if ($this->exists()) {
			$newMode = 0;
			$diffMode = $this->getModeByGroup("0000", $group, "4");
			if ($readable) {
				$newMode = $this->plusMode($diffMode);
			}
			else {
				$newMode = $this->minusMode($diffMode);
			}
			return chmod($this->filename, $newMode);
		}
		return false;
	}
	
	/**
	 * 设置文件只可以读
	 *
	 * @return boolean
	 */
	public function setReadOnly() {
		if ($this->exists()) {
			return chmod($this->filename, 0444);
		}
		return false;
	}

	/**
	 * 设定文件是否可写
	 *
	 * @param boolean $writable 是否可写
	 * @param integer $group 应用到的权限组
	 * @return boolean
	 */
	public function setWritable($writable, $group = IFile::ALL_PERMISSION) {
		if ($this->exists()) {
			$newMode = 0;
			$diffMode = $this->getModeByGroup("0000", $group, "2");
			if ($writable) {
				$newMode = $this->plusMode($diffMode);
			}
			else {
				$newMode = $this->minusMode($diffMode);
			}
			return chmod($this->filename, $newMode);
		}
		return false;
	}
	
	/**
	 * 返回文件的类型。可能的值有 fifo，char，dir，block，link，file 和 unknown。 
	 *
	 * @return string
	 */
	public function getType() {
		if ($this->exists()) {
			return filetype($this->filename);
		}
		return null;
	}
	
	/**
	 * 取得目录分隔符
	 *
	 * unix/linux为/，windows为\
	 * 
	 * @return string
	 */
	public static function getDirectorySeparator() {
		return DIRECTORY_SEPARATOR;
	}
	
	/**
	 * 清除文件缓存
	 */
	public static function clearCache() {
		clearstatcache();
	}
	
	/**
	 * 取得文件读对象
	 * 
	 * 可以使用它读取文件的内容
	 *
	 * @return IFileReader
	 */
	public function getReader() {
		return new IFileReader($this);
	}
	
	/**
	 * 取得文件写对象
	 * 
	 * 如果文件不存在，则创建新的文件；否则会先清空原来的文件内容
	 *
	 * @return IFileWriter
	 */
	public function getWriter() {
		return new IFileWriter($this);
	}
	
	/**
	 * 读取文件附加对象
	 * 
	 * 在文件末尾处附加新的内容
	 *
	 * @return IFileAppender
	 */
	public function getAppender() {
		return new IFileAppender($this);
	}
	
	/**
	 * 取得文件扩展名
	 * 
	 * 不含小数点"."符号
	 *
	 * @since 0.0.3
	 * @return string
	 */
	public function getExtension() {
		return pathinfo($this->filename, PATHINFO_EXTENSION);
	}
}

?>